﻿using System;
using System.Data;
using System.Diagnostics.CodeAnalysis;
using System.Text.RegularExpressions;

namespace Trade.Journal
{
    public enum BarPeriod { None, Second, Minute, Hour, Day, Week, Month, Year };

    /// <summary>
    /// 1S
    /// 5M
    /// 2H
    /// 1D
    /// 1W
    /// 1O(month)
    /// 1Y
    /// </summary>
    [Serializable]
    public readonly struct BarCycle : IEquatable<BarCycle>, IComparable<BarCycle>
    {
        private BarCycle(int interval, BarPeriod periodType) : this()
        {
            if (interval <= 0)
                throw new InvalidOperationException("Interval值不允许为0或负值");

            if (periodType == BarPeriod.None)
                throw new InvalidOperationException("Period值不允许为None");

            Interval = interval;
            PeriodType = periodType;
            DisplayValue = GetDisplay();
            Seconds = CalcNatualTime();
        }

        public BarCycle(BarPeriod period) : this(1, period) { }

        /// <summary>
        /// 长度
        /// </summary>
        public int Interval { get; }

        /// <summary>
        /// 类型
        /// </summary>
        public BarPeriod PeriodType { get; }

        public string DisplayValue { get; }

        internal int Seconds { get; }

        public override string ToString()
        {

            switch (PeriodType)
            {
                case BarPeriod.Second:
                    return $"{Interval}S";
                case BarPeriod.Minute:
                    return $"{Interval}M";
                case BarPeriod.Hour:
                    return $"{Interval}H";
                case BarPeriod.Day:
                    return $"{Interval}D";
                case BarPeriod.Week:
                    return $"{Interval}W";
                case BarPeriod.Month:
                    return $"{Interval}O";
                case BarPeriod.Year:
                    return $"{Interval}Y";
                default:
                    return "";
            }
        }

        public override int GetHashCode()
        {
            return Seconds;
        }

        /// <summary>
        /// 从字符串创建
        /// 5M 1H
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static BarCycle FromString(string str)
        {
            if (string.IsNullOrWhiteSpace(str))
                return default;

            switch (str.Trim())
            {
                case "日":
                    return new BarCycle(BarPeriod.Day);
                case "周":
                    return new BarCycle(BarPeriod.Week);
                case "月":
                    return new BarCycle(BarPeriod.Month);
                case "年":
                    return new BarCycle(BarPeriod.Year);
            }

            Match m = Regex.Match(str.Trim(), @"(\d+)(\w+)");
            if (m.Success)
                switch (m.Groups[2].Value)
                {
                    case "S":
                        return BarCycle.FromSeconds(int.Parse(m.Groups[1].Value));
                    case "M":
                        return BarCycle.FromMinutes(int.Parse(m.Groups[1].Value));
                    case "H":
                        return BarCycle.FromHours(int.Parse(m.Groups[1].Value));
                    case "D":
                    case "日":
                        return new BarCycle(int.Parse(m.Groups[1].Value), BarPeriod.Day);
                    case "W":
                    case "周":
                        return new BarCycle(int.Parse(m.Groups[1].Value), BarPeriod.Week);
                    case "O":
                    case "月":
                        return new BarCycle(int.Parse(m.Groups[1].Value), BarPeriod.Month);
                    case "Y":
                    case "年":
                        return new BarCycle(int.Parse(m.Groups[1].Value), BarPeriod.Year);
                }

            return default;
        }

        /// <summary>
        /// 从秒数创建，支持最大 86400=>1天
        /// </summary>
        /// <param name="seconds">最大 86400=>1天</param>
        /// <returns></returns>
        public static BarCycle FromSeconds(int seconds)
        {
            if (seconds <= 0)
                return default;

            if (seconds < 60)
                return new BarCycle(seconds, BarPeriod.Second);

            if (seconds % 60 != 0)
                throw new ArgumentException($"Invalid seconds {seconds}");
            if (seconds < 3600)
                return new BarCycle(seconds / 60, BarPeriod.Minute);

            if (seconds % 3600 != 0)
                throw new ArgumentException($"Invalid seconds {seconds}");
            if (seconds < 86400)
                return new BarCycle(seconds / 3600, BarPeriod.Hour);

            if (seconds == 86400)
                return new BarCycle(BarPeriod.Day);

            throw new ArgumentException("seconds Is Too Large, Only Support Period Below Day");
        }

        /// <summary>
        /// 从分钟数创建，支持最大 60=>1小时
        /// </summary>
        /// <param name="minutes">最大 60=>1小时</param>
        /// <returns></returns>
        public static BarCycle FromMinutes(int minutes)
        {
            if (minutes <= 0)
                return default;

            if (minutes < 60)
                return new BarCycle(minutes, BarPeriod.Minute);

            if (minutes % 60 != 0)
                throw new ArgumentException($"Invalid minutes {minutes}");
            if (minutes < 1440)
                return new BarCycle(minutes / 60, BarPeriod.Hour);

            if (minutes == 1400)
                return new BarCycle(BarPeriod.Day);

            throw new ArgumentException("minutes Is Too Large, Only Support Period Below Day");
        }

        /// <summary>
        /// 从小时数创建，支持最大24=>1天
        /// </summary>
        /// <param name="hour"></param>
        /// <returns></returns>
        public static BarCycle FromHours(int hour)
        {
            if (hour <= 0)
                return default;

            if (hour < 24)
                return new BarCycle(hour, BarPeriod.Hour);
            else if (hour == 24)
                return new BarCycle(BarPeriod.Day);

            throw new ArgumentException("hour Is Too Large, Only Support Period Below Day");
        }


        public bool Equals(BarCycle other)
        {
            return Seconds == other.Seconds;
        }

        public override bool Equals(object other)
        {
            if (other is BarCycle b)
            {
                return Seconds == b.Seconds;
            }
            if (other is string s)
            {
                BarCycle bt = BarCycle.FromString(s);
                if (bt == default)
                    return false;
                else
                    return Seconds == bt.Seconds;
            }
            else
                return false;
        }

        public static explicit operator BarCycle(string str)
        {
            return FromString(str);
        }

        public static bool operator ==(BarCycle barType, BarCycle other)
        {
            return barType.Equals(other);
        }
        public static bool operator !=(BarCycle barType, BarCycle other)
        {
            return !barType.Equals(other);
        }
        public static bool operator <(BarCycle val, BarCycle other)
        {
            return val.Seconds < other.Seconds;
        }
        public static bool operator >(BarCycle val, BarCycle other)
        {
            return val.Seconds > other.Seconds;
        }

        public static DateTime operator +(DateTime time, BarCycle barType)
        {
            switch (barType.PeriodType)
            {
                case BarPeriod.Year:
                    return time.AddYears(barType.Interval);
                case BarPeriod.Month:
                    return time.AddMonths(barType.Interval);
                case BarPeriod.Week:
                    return time.AddDays(barType.Interval * 7);
                case BarPeriod.Day:
                    return time.AddDays(barType.Interval);
                case BarPeriod.Hour:
                    return time.AddHours(barType.Interval);
                case BarPeriod.Minute:
                    return time.AddMinutes(barType.Interval);
                case BarPeriod.Second:
                    return time.AddSeconds(barType.Interval);

                default:
                    return time;
            }
        }
        public static DateTime operator -(DateTime time, BarCycle barType)
        {
            switch (barType.PeriodType)
            {
                case BarPeriod.Year:
                    return time.AddYears(-barType.Interval);
                case BarPeriod.Month:
                    return time.AddMonths(-barType.Interval);
                case BarPeriod.Week:
                    return time.AddDays(-barType.Interval * 7);
                case BarPeriod.Day:
                    return time.AddDays(-barType.Interval);
                case BarPeriod.Hour:
                    return time.AddHours(-barType.Interval);
                case BarPeriod.Minute:
                    return time.AddMinutes(-barType.Interval);
                case BarPeriod.Second:
                    return time.AddSeconds(-barType.Interval);

                default:
                    return time;
            }
        }


        /// <summary>
        /// 是否兼容other
        ///  
        /// 且this的周期是other周期的倍数
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public bool IsCompatible(BarCycle other)
        {
            if (PeriodType < other.PeriodType)
                return false;

            if (PeriodType == other.PeriodType)
                return Interval % other.Interval == 0;

            //
            if (BarPeriod.Hour >= PeriodType && PeriodType > other.PeriodType && other.PeriodType >= BarPeriod.Second)
                return (Interval * 60 * (PeriodType - other.PeriodType)) % (other.Interval * 60 * (PeriodType - other.PeriodType)) == 0;

            if (PeriodType >= BarPeriod.Day && other.PeriodType > BarPeriod.None)
                return true;

            return false;
        }

        /// <summary>
        /// 计算是Other的几倍
        /// 1.同类型返回
        /// 2.不同类型仅支持Day以下类型
        /// 不是整数则返回0
        /// </summary>
        /// <param name="other"></param>
        /// <returns></returns>
        public int CalcMultiple(BarCycle other)
        {
            if (PeriodType == other.PeriodType)
                if (Interval % other.Interval == 0)
                    return Interval / other.Interval;
                else return 0;

            if (PeriodType >= BarPeriod.Day || other.PeriodType >= BarPeriod.Day)
                return 0;

            if (Seconds % other.Seconds == 0)
                return Seconds / other.Seconds;
            else
                return 0;
        }


        /// <summary>
        /// 计算自然时间下的秒数
        /// 对于Day以上类型，仅参考
        /// </summary>
        /// <returns></returns>
        private int CalcNatualTime()
        {
#pragma warning disable CS8509 // Switch 表达式不会处理所有可能的输入（它并非详尽无遗）。
            return Interval * PeriodType switch
#pragma warning restore CS8509 // Switch 表达式不会处理所有可能的输入（它并非详尽无遗）。
            {
                BarPeriod.None => 0,
                BarPeriod.Second => 1,
                BarPeriod.Minute => 60,
                BarPeriod.Hour => 3600,
                BarPeriod.Day => 86400,
                BarPeriod.Week => 604800,//7天
                BarPeriod.Month => 2678400,//31天
                BarPeriod.Year => 31536000//365天
            };

        }

        /// <summary>
        /// 获取time对应的周期中的起始时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        [Obsolete]
        internal DateTime GetIntervalStartTime(DateTime time)
        {
            switch (PeriodType)
            {
                case BarPeriod.Year:
                    return new DateTime(time.Year, 1, 1);
                case BarPeriod.Month:
                    return new DateTime(time.Year, time.Month, 1);
                case BarPeriod.Week:
                    return new DateTime(time.Year, time.Month, time.Day + 1 - (int)time.DayOfWeek);
                case BarPeriod.Day:
                    return time.Date;
                case BarPeriod.Hour:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0);
                case BarPeriod.Minute:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute - time.Minute % Interval, 0);
                case BarPeriod.Second:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second - time.Second % Interval);

                default:
                    return time;
            }
        }

        /// <summary>
        /// 获取time对应的周期中的结束时间
        /// </summary>
        /// <param name="time"></param>
        /// <returns></returns>
        [Obsolete]
        internal DateTime GetIntervalEndTime(DateTime time)
        {
            switch (PeriodType)
            {
                case BarPeriod.Year:
                    return new DateTime(time.Year, 12, 31);
                case BarPeriod.Month:
                    return new DateTime(time.Year, time.Month + 1, 1).AddDays(-1);
                case BarPeriod.Week:
                    return new DateTime(time.Year, time.Month, time.Day + 5 - (int)time.DayOfWeek);
                case BarPeriod.Day:
                    return time.Date;
                case BarPeriod.Hour:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour + 1, 0, 0);
                case BarPeriod.Minute:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute - time.Minute % Interval, 0).AddMinutes(Interval);
                case BarPeriod.Second:
                    return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second - time.Second % Interval).AddSeconds(Interval);

                default:
                    return time;
            }
        }



        private string GetDisplay()
        {
            switch (PeriodType)
            {
                case BarPeriod.Second:
                    return $"{Interval}S";
                case BarPeriod.Minute:
                    return $"{Interval}";
                case BarPeriod.Hour:
                    return $"{Interval}H";
                case BarPeriod.Day:
                    if (Interval == 1)
                        return "日";
                    else
                        return $"{Interval}D";
                case BarPeriod.Week:
                    if (Interval == 1)
                        return "周";
                    else
                        return $"{Interval}W";
                case BarPeriod.Month:
                    if (Interval == 1)
                        return "月";
                    else
                        return $"{Interval}O";
                case BarPeriod.Year:
                    if (Interval == 1)
                        return "年";
                    else
                        return $"{Interval}Y";
                default:
                    return "";
            }
        }

        public int CompareTo(BarCycle other)
        {
            return Seconds - other.Seconds;
        }
    }
     
    public static class BarCycles
    {
        public static readonly BarCycle Y1 = new BarCycle(BarPeriod.Year);

        public static readonly BarCycle O1 = new BarCycle(BarPeriod.Month);

        public static readonly BarCycle W1 = new BarCycle(BarPeriod.Week);

        public static readonly BarCycle D1 = new BarCycle(BarPeriod.Day);

        public static readonly BarCycle H4 = BarCycle.FromHours(4);

        public static readonly BarCycle H1 = new BarCycle(BarPeriod.Hour);

        public static readonly BarCycle M30 = BarCycle.FromMinutes(30);

        public static readonly BarCycle M15 = BarCycle.FromMinutes(15);

        public static readonly BarCycle M5 = BarCycle.FromMinutes(5);

        public static readonly BarCycle M3 = BarCycle.FromSeconds(3 * 60);

        public static readonly BarCycle M1 = new BarCycle(BarPeriod.Minute);
    }

    

}
